#include "stdinc.h"
#include "CmdList.h"

#define FLAG_CMD	0x00000001
#define FLAG_ALIAS	0x00000002

#include <string.h>

CCmdList::CCmdList()
{
}

CCmdList::~CCmdList()
{
}

bool CCmdList::AddCmd(char* name, TCmdFunc func, int param)
{
	TCmd cmd;
	if (m_commands.Find(name, cmd))
	{
		LOG->Console("Name \"%s\" already assigned", name);
		return false;
	}

	cmd.asFunc = func;
	cmd.param = param;
	cmd.flags = FLAG_CMD;

	m_commands.Add(name, cmd);
	return true;
}

bool CCmdList::AddCmdList(TCmdStruct* list)
{
	int i = 0;

	while (list[i].name)
	{
		if (!AddCmd(list[i].name, list[i].func, list[i].param))
			return false;
		i++;
	}

	return true;
}

bool CCmdList::AddAlias(char* name, char* alias)
{
	if (!IsValidName(name))
		return false;

	TCmd cmd;

	if (m_commands.Find(name, cmd))
	{
		if (cmd.flags & FLAG_CMD)
			return false;
	}

	char* txt = new char[strlen(alias)+1];
	strcpy(txt, alias);

	cmd.asAlias = txt;
	cmd.param = 0;
	cmd.flags = FLAG_ALIAS;

	m_commands.Add(name, cmd);
	return true;
}

bool CCmdList::Exec(Parser* parser)
{
	char name[65];
	int i = 0;

	while (1)
	{
		if (!parser->GetToken(name, 65))
			return true;

		TCmd cmd;

		if (!m_commands.Find(name, cmd))
		{
			LOG->Console("Unknown command \"%s\"", name);
			return false;
		}

		if (cmd.flags & FLAG_ALIAS)
		{
			if (!parser->EndCmd())
			{
				LOG->Console("Alias \"%s\" can't have params", name);
				return false;
			}

			if (!Exec(cmd.asAlias))
				return false;
		}
		else
		{
			TCmdFunc func = cmd.asFunc;

			int flag = 0;
			if (name[0] == '+')
				flag = FLAG_PLUS;
			if (name[0] == '-')
				flag = FLAG_MINUS;

			if (!func(flag, parser))
				return false;
		}

		if (!parser->EndCmd())
		{
			LOG->Console("Expecting ';' after command \"%s\"", name);
			return false;
		}

		parser->SkipEnd();
	}
}

bool CCmdList::Exec(char* text)
{
	Parser parser(text);

	return Exec(&parser);
}

bool CCmdList::IsValidName(char* name)
{
	int p = 0;

	if ((name[0] == '+') || (name[0] == '-'))
		p++;

	while (name[p])
	{
		if (((name[p]>='a')&&(name[p]<='z')) ||
			((name[p]>='A')&&(name[p]<='Z')) ||
			((name[p]>='0')&&(name[p]<='9')) ||
			(name[p] == '_')
			)
		{
			p++;
		}
		else
		{
			LOG->Console("Invalid char '%c' in name \"%s\"", name[p], name);
			return false;
		}
	}
	return true;
}

CCmdList::Parser::Parser(char* text)
{
	m_text = text;
	m_pos = 0;
}

void CCmdList::Parser::SkipWhitespace()
{
	while (m_text[m_pos] == ' ')
	m_pos++;
}

bool CCmdList::Parser::GetToken(char* text, int maxLen)
{
	int p;

	SkipWhitespace();

	if (m_text[m_pos] != '"')
	{
		p = 0;
		while ((m_text[m_pos] != ' ') && (m_text[m_pos] != ';') && m_text[m_pos])
		{
			text[p++] = m_text[m_pos++];
			if (p==maxLen)
			{
				text[p] = 0;
				LOG->Console("Token too long \"%s...\"", text);
				return false;
			}
		}
		text[p] = 0;

		return (p>0);
	}

	m_pos++;
	p = 0;
	while ((m_text[m_pos] != '"') && m_text[m_pos])
	{
		if (m_text[m_pos]=='\\')
		{
			m_pos++;
			switch (m_text[m_pos])
			{
			case '"':
				text[p++] = m_text[m_pos++];
				break;
			case '\\':
				text[p++] = m_text[m_pos++];
				break;
			default:
				LOG->Console("Invalid switch '\\%c'", m_text[m_pos]);
				return false;
			}
		}
		else
			text[p++] = m_text[m_pos++];

		if (p==maxLen)
		{
			text[p] = 0;
			LOG->Console("Token too long \"%s...\"", text);
			return false;
		}
	}

	return true;
}

bool CCmdList::Parser::GetFloat(float& val)
{
	bool neg = false;
	int n = 0;
	val = 0;

	SkipWhitespace();

	if (m_text[m_pos] == '-')
	{
		m_pos++;
		neg = true;
	}

	while ((m_text[m_pos] >= '0') && (m_text[m_pos] <= '9'))
	{
		val = val*10 + (m_text[m_pos] - '0');
		m_pos++;
		n++;
	}

	if (m_text[m_pos] == '.')
	{
		m_pos++;
		float v = 0.1f;

		while ((m_text[m_pos] >= '0') && (m_text[m_pos] <= '9'))
		{
			val += v * (m_text[m_pos] - '0');
			v /= 10.0f;
			m_pos++;
			n++;
		}
	}

	if (n<1)
	{
		LOG->Console("Invalid number format");
		return false;
	}

	if (neg)
		val = -val;

	if ((m_text[m_pos] == ' ') || (m_text[m_pos] == ';') || (m_text[m_pos] == 0))
		return true;

	LOG->Console("Unexpected char '%c' in number", m_text[m_pos]);
	return false;
}

bool CCmdList::Parser::GetInt(int& val)
{
	bool neg = false;
	int n = 0;
	val = 0;

	SkipWhitespace();

	if (m_text[m_pos] == '-')
	{
		m_pos++;
		neg = true;
	}

	while ((m_text[m_pos] >= '0') && (m_text[m_pos] <= '9'))
	{
		val = val*10 + (m_text[m_pos] - '0');
		m_pos++;
		n++;
	}

	if (n<1)
	{
		LOG->Console("Invalid number format");
		return false;
	}

	if (neg)
		val = -val;

	if ((m_text[m_pos] == ' ') || (m_text[m_pos] == ';') || (m_text[m_pos] == 0))
		return true;

	LOG->Console("Unexpected char '%c' in number", m_text[m_pos]);
	return false;
}

bool CCmdList::Parser::GetBool(bool& val)
{
	SkipWhitespace();

	bool ok = false;

	if (m_text[m_pos] == '0')
	{
		val = false;
		ok = true;
	}

	if (m_text[m_pos] == '1')
	{
		val = true;
		ok = true;
	}

	char tmp[6];

	if (GetToken(tmp, 5))
	{
		if (!stricmp(tmp, "true"))
		{
			val = true;
			ok = true;
		}

		if (!stricmp(tmp, "false"))
		{
			val = false;
			ok = true;
		}
	}

	if (!ok)
	{
		LOG->Console("Expecting boolean");
		return false;
	}

	if ((m_text[m_pos] == ' ') || (m_text[m_pos] == ';') || (m_text[m_pos] == 0))
		return true;

	LOG->Console("Unexpected char '%c' in boolean", m_text[m_pos]);
	return false;
}

bool CCmdList::Parser::GetString(char*& val)
{
	char tmp[512];

	if (!GetToken(tmp, 512))
	{
		LOG->Console("Expecting string");
		return false;
	}

	val = strdup(tmp);
	return true;
}


bool CCmdList::Parser::EndCmd()
{
	SkipWhitespace();

	if ((m_text[m_pos] == ';')||(m_text[m_pos] == 0))
		return true;

	return false;
}

void CCmdList::Parser::SkipEnd()
{
	while ((m_text[m_pos] == ';') || (m_text[m_pos] == ' '))
		m_pos++;
}
